Casting

  • Casting is the act of reinterpreting or converting a value from one type to another.

Runtime casting
float to_float(int x) {
    return (float)x;
}
convert :: proc(x: int) -> f32 {
    return f32(x) // runtime conversion
}
  • If x  is not known at compile time, CPU executes a conversion instruction.

Value Transformation (conversion)

  • Changes representation

  • May require CPU instructions

  • There's runtime cost unless optimized away

int β†’ float
u8 β†’ u32
int  to f32
  • Integer 32-bit

00000000 00000000 00000000 00001010   // 10
  • Float 32-bit IEEE-754

sign | exponent | mantissa
 0   | 10000010 | 01000000000000000000000   // 10.0
  • Precision implications :

    • All integers up to 2^24 (16,777,216)  are exact.

    • Beyond that, precision loss occurs:

    int x = 16777217;
    float y = (float)x;
    
    • y  becomes 16777216 .

  • If can be computed at comptime (constant) :

    • Done by the compiler. Zero cost at runtime.

  • Runtime dedicated instructions in the floating-point unit (FPU) :

    • On x86 (SSE/AVX):

      cvtsi2ss xmm0, eax   ; int β†’ float
      
    • On ARM (NEON/VFP):

      scvtf s0, w0         ; signed int β†’ float
      
    • Usually 1–3 CPU cycles

    • Uses the FPU / SIMD unit

    • May cause pipeline latency and register domain crossing (int β†’ float regs)

    • Very fast, but not zero-cost.

  • Steps :

    • From 10  ( int ) to 10.0  ( f32 ).

    1. Determine sign

      • 10  β†’ positive β†’ sign = 0

    2. Convert to binary scientific notation

      • 10 = 1010β‚‚ = 1.010 Γ— 2Β³

    3. Compute exponent

      • Bias for f32  = 127

      • Stored exponent = 3 + 127 = 130  β†’ 10000010

    4. Fill mantissa

      • Drop leading 1.  β†’ store 01000000000000000000000

    • Result:

      0 | 10000010 | 01000000000000000000000
      

Type Reinterpretation

  • Often zero-cost

  • Dangerous if misused

  • Type-checked at compile time

  • "Are bitcasts purely a compiler thing?"

    • Mostly yesβ€”but not entirely.

    • When types live in different register classes:

    int x;
    float y = bitcast(float, x);
    
    • Compiler may emit:

    movd xmm0, eax   ; move bits from int reg β†’ float reg
    
    • Bits unchanged, but instruction needed due to register domains.

Pointer casting / reinterpretation
  • Under the hood: there is NO difference in representation. The only difference is how the compiler interprets that address.

Vec3* a;
void* b;
a = 0x1000
b = 0x1000
void* p = ...;
int* ip = (int*)p; // pointer reinterpretation
struct Vec3 { float x, y, z; };
Vec3* p; //typed pointer
  • A pointer is just something as 0x7FFEA0123450 . The CPU does not know or care whether this points to an int, a struct, raw bytes, etc.

  • A typed pointer is just for the compiler.

    • "At this address, there is a Vec3 laid out in memory.".

    • This allows for:

      1. Correct dereferencing

        • p->x

        • Compiler generates:

        • load from address + offset_of(x)

      2. Pointer arithmetic

        • p + 1

        • Becomes:

        • address + sizeof(Vec3)

        • A pointer without typing ( rawptr , void* ) can't perform pointer arithmetic, as "+1 what? 1 byte? 1 struct? 1 element?".

      3. Type checking

        • Prevents invalid access (at compile time)

  • A rawptr  / void*  is interpreted by the compiler as just an address, it doesn't know what is in there.

Integer Casting with same register sizes
  • Valid for :

    • u32   <-> i32 .

    • u64   <-> i64 .

    • uint  <-> int .

  • Example :

    u32 a = 0xFFFFFFFF;
    i32 b = (i32)a;
    
    • The bits are:

    11111111 11111111 11111111 11111111
    
    • As u32  β†’ 4294967295

    • As i32  β†’ -1

    • Nothing changed except interpretation.

    • The compiler typically does nothing. It reuses the same register and emits no instructions.